package splash.core.connection;

import java.net.Socket;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;

import java.text.ParseException;

import splash.core.Settings;
import splash.core.logging.Logger;
import splash.core.security.UserList;
import splash.core.execution.Execute;
import splash.core.Util;

public class ConsoleSession implements Runnable
{
	private Socket socket;
	private Logger log;
	private Settings settings;
	private ConsoleUI cui;
	private int maxHeaders;
	private UserList userList;

        private InputStream is;
        private OutputStream os;
	private BufferedReader input;
	private PrintWriter output;
	
	private ResponseCode responseCode;
	private int messageId;
	private String sessionId;
	private PropertySet ps;
	private Response response;
	
	public ConsoleSession(Socket s, Logger l, Settings st, ConsoleUI cui, UserList u)
	{
		if(s == null || l == null || st == null)
			throw new NullPointerException();
			
		this.socket = s;
		this.log = l;
		this.settings = st;
		this.cui = cui;
		this.userList = u;
		
		/* Will be hardcoded */
		this.maxHeaders = 100;
	}

        //@Override
	public void run()
	{
		this.responseCode = ResponseCode.OK;
		int headersCount = 0;
		
		try
		{
			/* Init I/O */
                        is = socket.getInputStream();
                        os = socket.getOutputStream();
			input = new BufferedReader(new InputStreamReader(is));
			output = new PrintWriter(os, true);
			
		}
		catch(IOException e)
		{
			/* Failed to set */
		}
		
		/* If I/O is established base response */
		response = new Response();
		
		try
		{
			/* Load request headers */
			ps = new PropertySet();

                        long timeout = System.currentTimeMillis() + (1000 *
                                settings.getTimeout());

                        /* <n> s to load headers - is set in Settings class */
			while(System.currentTimeMillis() < timeout)
			{
				if(headersCount == this.maxHeaders)
				{
					this.responseCode = ResponseCode.REQUEST_TOO_LONG;
					break;
				}
				String ln = input.readLine();

                                if(ln == null || ln.equals(""))
                                        continue;

                                if(ln.equalsIgnoreCase("END"))
                                    break;
                                
                                Property np = Property.parseProperty(ln);
                                ps.add(np);

                                headersCount++;
				
			}
		}
		catch(ParseException pe)
		{
			/* Bad string (header) */
                        /* Log it */
                        log.printError("Slab zahtevek iz " + socket.getInetAddress());
			this.responseCode = ResponseCode.BAD_REQUEST;
                        //pe.printStackTrace();
		}
                catch(IOException ioe)
                {
                        ioe.printStackTrace();
                }
		
		/* If everything is still OK */
		if(this.responseCode == ResponseCode.OK)
		{
			/* Check for session */
			String ssid = null;
			String mid = null;
			String cmd = null;
			
			for(int i = 0; i < ps.size(); i++)
			{
				if(ps.get(i).getName().toLowerCase().equals("ssid"))
					ssid = ps.get(i).getValue();
				
				if(ps.get(i).getName().toLowerCase().equals("mid"))
					mid = ps.get(i).getValue();
				
				if(ps.get(i).getName().toLowerCase().equals("cmd"))
					cmd = ps.get(i).getValue();
			}
			
			if(ssid != null && mid != null && cmd != null)
			{
				
				/* Check for session auth */
                                Session ts = cui.getSession(ssid);
                                if(ts != null && ts.getAuth())
                                {
                                        /* User is logged in */
                                        this.responseCode = handle(ts, cmd);
                                        response.addProperty(new Property("mid",
                                                mid));

                                }
                                else
                                {
                                        /* No session error */
                                        log.printError("Zahtevana neobsotječa seja z naslova " + socket.getInetAddress());
                                        this.responseCode = ResponseCode.NO_SESSION;
                                }
				
			}
			else
			{
				/* Check for login */
				
				String username = null;
				String password = null;
				
                                /* Will keep it for backward compatibility with heavy edition */
				for(int i = 0; i < ps.size(); i++)
				{
					if(ps.get(i).getName().toLowerCase().equals("username"))
						username = ps.get(i).getValue();
						
					if(ps.get(i).getName().toLowerCase().equals("password"))
						password = ps.get(i).getValue();				
				}
				
				if(password != null && username != null)
				{
					/* Login user */
					String auth = loginUser(username, password);
					if(auth != null)
					{
						/* Because you never know */
						this.sessionId = auth;
						this.responseCode = ResponseCode.OK;
						response.addProperty(new Property("ssid",this.sessionId));
					}
					else
					{
                                                /* Wrong username or password */
                                                /* Limit accesses to specific username */
                                                log.printError("Napacno uporabnisko ime ali geslo z naslova " + socket.getInetAddress());
						this.responseCode = ResponseCode.BAD_LOGIN;
					}
					
				}
				else
				{
					/* Bad request */
					this.responseCode = ResponseCode.BAD_REQUEST;
				}
			}
		}
		
		/* Form response */
		PropertySet rps = response.getPropertySet();
		
		/* Place status (reponse code) on the begining */
		rps.add(0, new Property("status", "" + this.responseCode));
		
		/* If necessarily will add header count */
		/* rps.add(new Property("Headers", "" + rps.count() + 1), 0); */
		
		/* Send response code */
		for(int i = 0; i < rps.size(); i++)
		{
			if(!output.checkError())
			{
				output.print(rps.get(i).generateString() + "\r\n");
			}
		}

                output.print("END" + "\r\n");
                
                if(this.settings.getHeaders())
                {
                    System.out.println("Request:");
                    for(int i = 0; i < this.ps.size(); i++)
                    {
                        System.out.print(this.ps.get(i).getName() + ": " + 
                                this.ps.get(i).getValue());
                    }
                    System.out.println("\nResponse:");
                    for(int i = 0; i < this.response.getPropertySet().size(); i++)
                    {
                        System.out.println(
                                this.response.getPropertySet().get(i).getName() 
                                + ": " +
                                this.response.getPropertySet().get(i).getValue());
                    }
                }
		
		/* Close */
                try
                {
                    output.close();
                    input.close();
                    socket.close();
                }
                catch(IOException e)
                {}
                
                /* decrement clients count */
                /* should be synchronized */
                cui.setClientCount(cui.getClientCount() - 1);
	}
	
	private String loginUser(String username, String password)
	{
		/* Useless? */
		if(username == null || password == null)
			return null;

		/* Check if valid login data */
                for(int i = 0; i < userList.size(); i++)
                    if(userList.get(i).getUsername().equals(username) &&
                        userList.get(i).getPassword().equals(Util.sha(password)))
                    {
                            /* Register session */
                            Session ssn = Session.create();
                            while((cui.registerSession(ssn)) == null)
                            {
                                    ssn = Session.create();
                            }

                            this.sessionId = ssn.getSSID();
                            Session link = cui.getSession(this.sessionId);
                            link.setAuth(true);
                            link.setUser(userList.get(i));
                            
                            return this.sessionId;
                    }
                    else
                    {
                        System.out.println(username + "::" + Util.sha(password));
                        System.out.println("vs.");
                        System.out.print(userList.get(i).getUsername() + "::");
                        System.out.println(userList.get(i).getPassword());
                    }

		return null;
	}

        private ResponseCode handle(Session s, String cmd)
        {
                Command c;
                try
                {
                    c = new Command(cmd);
                }
                catch(NoCommandException e)
                {
                    /* No such command */
                    return ResponseCode.NOT_IMPLEMENTED;
                }

                /* No security management is present in lite edition */
                
                /* Log execution */
                log.printInfo("Izvajam ukaz \"" + cmd + "\" z naslova " + socket.getInetAddress());
                
                /* Execute command */
                return Execute.executeCommand(c, ps, this);
        }

        public Response getResponse()
        {
            return this.response;
        }
        
        /* For upper level control */
        public Logger getLogger()
        {
            return this.log;
        }
        
        public Settings getSettings()
        {
            return this.settings;
        }
        
        public ConsoleUI getConcoleUI()
        {
            return this.cui;
        }
        
        public PropertySet getPropertySet()
        {
            return this.ps;
        }
        
        public String getSSID()
        {
            return this.sessionId;
        }
}